home *** CD-ROM | disk | FTP | other *** search
Wrap
/* This file demonstrates some slightly sophisticated drawing, as well as basic use of files in MacStarter. To use this file, you should add it to the MacStarter.╣ project and delete the current applicationProcs file. The file inputBoxes.c must also be present in the MacStarter.╣ project window. When using this program, the user draws in a window using the mouse. The curve traced by the mouse is drawn, along with its seven "reflections", giving a pretty(?), symmetric pattern. Sketches can be saved to files and reloaded. All places where this file has been modified (from the original applicationProcs.c) are commented with comments that begin with // */ #include "globals-MacStarter.h" #include "inputBoxes.h" long gEventWaitTime = 100000; MenuHandle editMenu, fileMenu; void InitApplication(void); void UpdateMenus(void); void DoEditMenu(int itemNum); void DoFileMenu(int itemNum, int* done); void DoOtherMenu(int menuID, int itemNum); void ApplicationIdle(void); void CleanUpApplication(void); void AboutBox(void); void DoNewCommand(void); // define a symbolic constant specifying the maximum number of points // on the curves drawn by the user in a single window. #define maxPoints 1000 short WindowCt = 0; // to keep track of the number of windows opened // by the program; see function DoNewCommand below. void doOpen(void); // Declare a function to carry out Open Command class myWindow : public xWindow { public: // (Note: not all this stuff needs to be public; I am being lazy here. // It was easiest to make everything public when I kept getting // "access denied" errors, rather that improve the design. This is // not so bad in a small program that doesn't have to be maintained, // I guess.) // define the data that is relevant to the contents of this particular window: short ptCount; // how many points have been drawn (0 to maxPoints) short allFilledUp; // this is initialized to 0 and is set to 1 when // the number of points first reaches maxPoints // (It exists so that I can avoid giving the // user more than one "all filled up" message // per window.) Point pt[maxPoints]; // A list of the points drawn by the user. As the // user moves the mouse, a list of points traversed // is kept. The curve that is drawn is just // obtained by connecting these points. // The special value of pt[i].h = -1 separates // sequences of points on different curves. short winWidth,winHeight; // When the window changes size, the points in // array pt have to be scaled to the new size // these variables store the width and height // by which the points are curently scaled. // (These values are changed in function // adjustToNewSize, where the scaling is done.) void doClear(void); // Function to respond to Clear command void doSave(void); // Function to respond to Save command. // (Note: I put doClear and doSave inside the // window definition because they are directed at // a particular window. The commands New and // Open are not directed to a particular window, // so their handlers are not part of the window // definition.) void PutLineSegment(Point pt1, Point pt2); // Draws a line from pt1 to pt2 along with the seven // possible "reflections" of the line. virtual void OpenInRect(Str255 title, int left, int top, int right, int bottom); virtual short Close(void); virtual void adjustToNewSize(void); virtual void SetDefaults(void); virtual void doKey(char ch); virtual void doContentClick(Point localPt); virtual void doRedraw(Rect* badRect); virtual void doHScroll(int dh); virtual void doVScroll(int dv); virtual void doActivate(int active); }; // Function doClear is called in response to a Clear command from the // edit menu. It just clears the window by declaring that there are no // points for the window. NOTE the use of ForceRedraw(0), which causes the // window to be erased and redrawn by the functions with those specific // jobs. This is generally preferable to doing the redrawing "in place", // since that can lead to unnecessary code duplication. void myWindow::doClear(void) { ptCount = 0; allFilledUp = 0; ForceRedraw(0); } // Function doSave is called in response to the Save command from the // file menu. It uses function OpenNewFile (defined in inputBoxes.c) // to select the new file's name and folder using a standard Mac new file // dialog. (I should probably do more error checking here but in fact, // an error is unlikely.) void myWindow::doSave(void) { FILE *file; Str255 title; short i; GetTitle(title); if ( file = OpenNewFile("\pSave sketch in:",title) ) { SetTitle(title); fprintf(file, "%hd %hd %hd\n", winWidth, winHeight, ptCount); for (i=0; i<ptCount; i++) fprintf(file, "%hd %hd\n", pt[i].h, pt[i].v); fclose(file); }; } // Function doOpen is called in response to the Save command from the // file menu. It used function OpenOldFile (defined in inputBoxes.c) // to allow the user to specify the file to be read. // I do a fair amount of error-checking here because it is possible // for the user to select any text file, and if the file is not one // produced previously by this program, it will almost certainly generate // an error. void doOpen(void) { FILE *file; Str255 title; short width,height,ptCt; short h,v; short i; myWindow *win; if ( file = OpenOldFile(title) ) { win = new myWindow; win->Open(title); // note that the window title is the file name // that is returned by OpenOldFile if (fscanf(file,"%hd %hd %hd\n", &width, &height, &ptCt) != 3 || width <= 0 || height < 0 || ptCt > maxPoints) { TellUser("\pSorry, an error occured while trying to read from the file."); win->Close(); fclose(file); return; } else { win->winHeight = height; win->winWidth = width; win->ptCount = ptCt; }; for (i=0; i<win->ptCount; i++) { if (fscanf(file, "%hd %hd\n", &h, &v) != 2 || h < -1 || v < 0 || v>height || h>width ) { TellUser("\pSorry, illegal data or end-of-file encountered while trying to read from the file."); win->Close(); fclose(file); return; } else { win->pt[i].h = h; win->pt[i].v = v; } }; fclose(file); win->adjustToNewSize(); } } void myWindow::SetDefaults(void) { inherited::SetDefaults(); features = hasGoAway + hasZoom + hasGrow; // modify the feature list; // I don't want any scroll bars, but I do want a close box, // zoom box and grow box ptCount = 0; // initialize window data; there are initially no points allFilledUp = 0; // and the window is NOT allFilledUp. minH = 100; // set minimum window size when user drags "grow box" minV = 100; } void myWindow::OpenInRect(Str255 title, int left, int top, int right, int bottom) { inherited::OpenInRect(title,left,top,right,bottom); } short myWindow::Close(void) { inherited::Close(); } void myWindow::doKey(char ch) { } // Function PutLineSegment is used by doRedraw and by doContentClick to // draw a line segment and its seven possible "reflections" (using horizontal, // vertical and diagonal reflection). (I put reflections in quotes since // the diagonal "reflection" is not really a true reflection if the window // is not square.) This function assumes that the drawing port is already // set to be this window. void myWindow::PutLineSegment(Point pt1, Point pt2) { short a,b,x,y; a = pt1.h; // first, plot the line itself and its horizontal, b = pt1.v; // vertical and combined horizontal/vertical reflection x = pt2.h; y = pt2.v; MoveTo(a,b); LineTo(x,y); MoveTo(winWidth-a,b); LineTo(winWidth-x,y); MoveTo(winWidth-a,winHeight-b); LineTo(winWidth-x,winHeight-y); MoveTo(a,winHeight-b); LineTo(x,winHeight-y); a = pt1.v * ((double) winWidth )/ winHeight; // next, compute the diagonal b = pt1.h * ((double) winHeight )/ winWidth; // "reflection" of the original x = pt2.v * ((double) winWidth )/ winHeight; // line and plot it and its y = pt2.h * ((double) winHeight )/ winWidth; // reflections MoveTo(a,b); LineTo(x,y); MoveTo(winWidth-a,b); LineTo(winWidth-x,y); MoveTo(winWidth-a,winHeight-b); LineTo(winWidth-x,winHeight-y); MoveTo(a,winHeight-b); LineTo(x,winHeight-y); } void myWindow::doContentClick(Point localPt) { Point lastPt, nextPt; short didSomeDrawing; if (allFilledUp) // drawing after allFilledUp is not allowed return; lastPt = localPt; // starting point of curve didSomeDrawing = 0; while (StillDown()) { // continue while the user holds down the mouse button GetMouse(&nextPt); // read mouse location if (nextPt.h < 0) // clip mouse location to make sure it is in window nextPt.h = 0; else if (nextPt.h > winWidth) nextPt.h = winWidth; if (nextPt.v < 0) nextPt.v = 0; else if (nextPt.v > winHeight) nextPt.v = winHeight; if ( (nextPt.h-lastPt.h > 1) // test if the new point is || (nextPt.h-lastPt.h < -1) // not too close to last point; || (nextPt.v-lastPt.v > 1) // this avoids storing || (nextPt.v-lastPt.v < -1) ) { // too many points if (ptCount == maxPoints) { TellUser("\pEach window can only have a certain amount of data. This window's data space is now completely filled. You can't do any more drawing in it."); allFilledUp = 1; // out of room; stop drawing return; }; pt[ptCount++] = lastPt; // record a point in data array PutLineSegment(lastPt,nextPt); // and draw the line and reflections lastPt = nextPt; didSomeDrawing = 1; // remember that at least one point was recorded }; }; if (didSomeDrawing && ptCount < maxPoints) pt[ptCount++].h = -1; // if any points were stored, put a sentinel // in the array to separate this curve from the // next one the user might draw (so that they // are not connected when the window is redrawn) } void myWindow::doRedraw(Rect* badRect){ short i; if (ptCount > 0) // redraw any curves entered previously by user for (i=1; i<ptCount; i++) if (pt[i].h != -1 && pt[i-1].h != -1) // an h value of -1 is not PutLineSegment(pt[i-1],pt[i]); // actually on a curve; this is // a sentinel value between curves } void myWindow::adjustToNewSize(void) { Rect oldRect,newRect; short i; inherited::adjustToNewSize(); // Rescale the points to fit correctly into the newly resized window; // be careful not to scale the sentinel points with h = -1 as these // are not really points, but are only there to separate data for // different curves. (NOTE: because coordinates are integers, // the scaling done here involves some rounding errors, which // introduces some distortions into the image.) SetRect(&oldRect,0,0,winWidth,winHeight); SetRect(&newRect,0,0,theWindow->portRect.right - 1,theWindow->portRect.bottom - 1); // These rectangles are required for the Mac toolbox function ScalePt // which is used to do the scaling. oldRect is the old window rectangle, // and newRect is the window rectangle after resizing. (The size of the // old rectangle has to be remembered in the window data variables // winHeight and winWidth.) for (i=0; i<ptCount; i++) { // scale the points if (pt[i].h != -1) ScalePt(pt+i, &oldRect, &newRect); }; winWidth = newRect.right; // record current window size winHeight = newRect.bottom; } void myWindow::doHScroll(int dh) { inherited::doHScroll(dh); } void myWindow::doVScroll(int dv) { inherited::doVScroll(dv); } void myWindow::doActivate(int active) { inherited::doActivate(active); } void InitApplication(void) { MenuHandle appleMenu; fileMenu = GetMHandle(2); editMenu = GetMHandle(3); appleMenu = GetMHandle(1); SetItem(appleMenu,1,"\pAbout KaleidoSketch..."); // Program name for Apple Menu SetItem(fileMenu,1,"\pNew"); // Replaces "New Window" in file menu // (Seems better for a program that uses files.) InsMenuItem(fileMenu, "\pSave/S;Open/O", 1); // Add two file-oriented commands as entries #2 and 3 in file // menu; (Note that you could use ResEdit to build menus.) // Note that the item numbers for Quit and Close command // are changed, which forces some changes in functions // UpdateMenus and DoFileMenu. DoNewCommand(); } void UpdateMenus(void) { short i; WindowPtr win; xWindow *xwin; win = FrontWindow(); if ( win && ((WindowPeek)win)->windowKind < 0 ) { EnableItem(editMenu,1); for (i=3; i<7; i++) EnableItem(editMenu,i); } else { DisableItem(editMenu,1); for (i=3; i<6; i++) // #6 ("Clear") is handled below DisableItem(editMenu,i); } if (win && xWindow::Window2XWindow(win,&xwin)) { // the frontmost window is one belonging to this program, so I want // to enable the commands that are window-directed, such as Close, // and possibly Clear and Save (if the window is not empty). EnableItem(fileMenu,4); // Close Window Command // The next line checks whether anything has been drawn in the front // window. The type-cast (myWindow*) is required to get access to // ptCount, since the compiler knows only that xwin is an xWindow. if ( ((myWindow*)xwin)->ptCount > 0 ) { EnableItem(fileMenu,3); // Save Command EnableItem(editMenu,6); // Clear Command } else { // front window is empty; nothing to save or clear DisableItem(fileMenu,3); DisableItem(editMenu,6); } } else { // No front window to close, save, or clear DisableItem(fileMenu,3); DisableItem(fileMenu,4); DisableItem(editMenu,6); } } void DoEditMenu(int itemNum) { // having decided to use the Clear command from the Edit menu in my // program, I have to handle it here. Note again the use of type- // casting, which is necessary because Window2XWindow returns an // xWindow, not a myWindow. // Note: The test in the first line should always be true; If UpdateMenus // is correct, the Clear command will be disabled unless it is appropriate xWindow *xwin; if ( itemNum == 6 && xWindow::Window2XWindow(FrontWindow(),&xwin) ) ((myWindow*)xwin)->doClear(); } void DoFileMenu(int itemNum, int* done) { // This function has been modified to reflect the new commands (Save and // Open) added to the file menu in InitApplication. xWindow *win; if (itemNum == 6) // changed number of quit command from 4 to 6 *done = 1; else if (itemNum == 1) DoNewCommand(); else if (itemNum == 2) doOpen(); else if (itemNum == 3 && xWindow::Window2XWindow(FrontWindow(),&win)) ((myWindow*)win)->doSave(); else if (itemNum == 4 && xWindow::Window2XWindow(FrontWindow(),&win)) win->Close(); } void DoOtherMenu(int menuID, int itemNum) { } void ApplicationIdle(void) { } void CleanUpApplication(void) { } void AboutBox(void) { // The strings in the call to ParamText have been modified to describe // this program. (Again, this is not a secure way of identifying your // program, as noted in the comments on file applicationProcs.c.) ParamText( "\pKaleidoSketch", "\pDavid Eck", "\pHobart and William Smith Colleges\rGeneva, NY 14456\rE-mail: eck@hws.bitnet", "\pThis program was written in THINK C to illustrate the use of a Macintosh application shell that I wrote."); Alert(128,0L); } void DoNewCommand(void) { // This function has been modified to keep track of how many windows have // been opened and to name them appropriately: Untitled 1, Untitled 2, ... // There are some tricks here because win->open(title) requires a // Pascal style string, but it is easiest to produce the title using // sprintf, which makes a C style string. This requires the conversion // CtoPstr and the type-cast of title in the call to sprintf. // This type-cast is required because Str255 is type (unsigned char)* // and sprintf requires char* (I think). // (By the way, sprintf requires that this file include stdio.h, which is // done indirectly through the header inputBoxes.h so it is not necessary // to include it explcitely.) myWindow *win; Str255 title; win = new myWindow; WindowCt++; sprintf((char*)title,"Untitled %i",WindowCt); CtoPstr(title); win->Open(title); }